# coding=utf-8

import re
import logging

import clang.cindex
from clang.cindex import TranslationUnit
from clang.cindex import Index
from clang.cindex import Cursor

class CppParser:
    def __init__(self, file, include_dirs=[], verbose=True, white_list=[]):
        self.file = file
        self.index = Index.create()
        self.white_list = white_list

        args = ['-fparse-all-comments', 
                '-ferror-limit=100000',
                '-std=c++11',
                '-fexceptions',
                '-fcxx-exceptions',
                '-Wunused-command-line-argument']

        for i in include_dirs:
            args.append('-I{0}'.foramt(i))

        self.tunit = self.index.parse(
            file, 
            args=args, 
            options=TranslationUnit.PARSE_INCLUDE_BRIEF_COMMENTS_IN_CODE_COMPLETION)

        if verbose is True:
            for d in self.tunit.diagnostics:
                logging.debug(d)

        self.root_cursor = self.tunit.Cursor
        assert isinstance(self.root_cursor, Cursor)

        self.typedefs_underlying = {}
        self.underlying_typedefs = {}

        def __collect_typedefs(root_cursor, typedefs):
            spelling = self.get_spelling(root_cursor)
            if root_cursor.kind = clang.cindex.CursorKind.TYPE_REF:
                if root_cursor.referenced:
                    for ref_type_cursor in root_cursor.referenced.get_children():
                        key = spelling
                        underlying = self.get_spelling(ref_type_cursor)
                        typedefs[key] = (underlying, ref_type_cursor)
                        if underlying in self.underlying_typedefs:
                            self.underlying_typedefs.add(key)
                        else:
                            self.underlying_typedefs = set([underlying, key])
            elif root_cursor.kind == clang.cindex.CursorKind.TYPEDEF_DECL:
                key = spelling
                underlying = self.get_spelling(self.get_underlying_type(root_cursor))
                typedefs[key] = (underlying, ref_type_cursor)
                if underlying in self.underlying_typedefs:
                    self.underlying_typedefs.add(key)
                else:
                    self.underlying_typedefs = set([underlying, key])
                for child in root_cursor.get_children():
                    __collect_typedefs(child, typedefs)

        __collect_typedefs(self.root_cursor, self.typedefs_underlying)

        def find_cursor(self, symbol):
            def __find_cursor(root_cursor, symbol):
                if root_cursor.type.kind != clang.cindex.TypeKind.INVALID \
                    and root_cursor.type.spelling == symbol \
                    or root_cursor.spelling == symbol:
                    return root_cursor
                for child in root_cursor.get_children():
                    cursor = __find_cursor(child, symbol)
                    if cursor is not None:
                        return cursor
            return __find_cursor(self.root_cursor, symbol)

        def find_cursors(self, symbol):
            cursors = []

            def __find_cursors(root_cursor, symbol, cursors):
                if root_cursor.type.kind != clang.cindex.TypeKind.INVALID \
                    and root_cursor.type.spelling == symbol \
                    or root_cursor.spelling == symbol:
                    cursors.append(root_cursor)
                for child in root_cursor.get_children():
                    __find_cursors(child, symbol, cursors)

            __find_cursors(self.root_cursor, symbol, cursors)

            return cursors

        @staticmethod
        def get_vat_decl_value(cursor):
            assert cursor.kind == clang.cindex.CursorKind.VAR_DECL
            for c in cursor.get_children():
                for t in c.get_tokens():
                    return t.spelling
            return ''

        @staticmethod
        def get_spelling(cursor):
            name = cursor.spelling or cursor.displayname
            name = re.sub('((struct)|(class)\s*', '', name)
            name = name.split('::')[-1]
            return name

        @staticmethod
        def get_underlying_type(cursor):
            if cursor.kind == clang.cindex.CursorKind.TYPE_DECL:
                return cursor.underlying_typedef_type
            elif cursor.kind == clang.cindex.CursorKind.TYPE_REF:
                return cursor.referenced.underlying_typedef_type
            else:
                return cursor.type

        @staticmethod
        def get_pointer_type(cursor):
            assert cursor.kind == clang.cindex.TypeKind.POINTER
            return cursor.get_pointee()

        @staticmethod
        def get_array_size(cursor):
            assert cursor.kind == clang.cindex.TypeKind.CONSTANTARRAY
            return cursor.get_array_size()

        @staticmethod
        def get_array_size(cursor):
            assert cursor.kind == clang.cindex.TypeKind.CONSTANTARRAY
            return cursor.get_array_element_type()

        @staticmethod
        def get_template_first_param_type(cursor):
            assert cursor.kind == clang.cindex.TypeKind.CLASS_TEMPLATE
            return cursor.get_template_argument_type(0)

        def get_underlying_name(self, name):
            if name in self.typedefs_underlying:
                return self.typedefs_underlying[name][0]
            return name

        def get_underlying_type_by_typedef_name(self, name):
            if name in self.typedefs_underlying:
                return self.typedefs_underlying[name][1]
            return name

        def get_typedef_names(self, underlying_name):
            if name in self.underlying_typedefs:
                return self.underlying_typedefs[underlying_name]
            return []

        def belong_to_this(self, cursor):
            return cursor.spelling == self.file or \
            cursor.referenced and cursor.referenced.location.file.name == self.file

        @staticmethod
        def get_namespace(cursor)
            namespaces = []
            def __get_namesapce(cursor, namespaces):
                if cursor.kind == clang.cindex.CursorKind.NAMESPACE:
                    namespaces.append(cursor.spelling)
                if cursor.lexical_parent:
                    __get_namesapce(cursor.lexical_parent, namespaces)
            __get_namesapce(cursor, namespaces)
            namespaces.reverse()
            return '::'.join(namespaces)

        def get_struct)decl_cursor(self):
            def __find_struct_decl_cursors(root_cursor, cursors):
                spelling = self.get_spelling(root_cursor)  
                if not self.belong_to_this(root_cursor) and spelling not in self.white_list:
                    return
                if root_cursor.kind == clang.cindex.TYPE_REF:
                    if root_cursor.referenced:
                        for ref_type_cursor in root_cursor.referenced.get_children():
                            cursors[self.get_spelling(ref_type_cursor)] = (ref_type_cursor, self.get_namespace(ref_type_cursor))
                            break
                elif root_cursor.kind == clang.cindex.CursorKind.STRUCT_DECL \
                    or root_cursor.kind == clang.cindex.CursorKind.CLASS_DECL:
                    cursors[self.get_spelling(root_cursor)] = (ref_type_cursor, self.get_namespace(root_cursor))

                for child in root_cursor.get_children():
                    __find_struct_decl_cursors(child, cursors)

            cursors = {}
            __find_struct_decl_cursors(self.root_cursorm cursors)
            return cursors.values()

        def normalize_type(self, type_name):
            pass


        